package com.youku.compiler;

import static com.youku.compiler.TokenType.*;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class Tokenizer {

	private static final Map<String, TokenType> keywordType;

	static {
		keywordType = new HashMap<>();
		keywordType.put("if", IF);
		keywordType.put("else", ELSE);
		keywordType.put("elsif", ELSIF);
		keywordType.put("switch", SWITCH);
		keywordType.put("case", CASE);
		keywordType.put("while", WHILE);
		keywordType.put("for", FOR);
		keywordType.put("and", AND);
		keywordType.put("or", OR);
		keywordType.put("class", CLASS);
		keywordType.put("extends", EXTENDS);
		keywordType.put("def", DEF);
		keywordType.put("true", TRUE);
		keywordType.put("false", FALSE);
	}

	private List<Token> tokens;

	private String input;

	private int pos;

	private int mark;

	private State state = State.START;

	// 行数
	private int rowNum = 1;

	public Tokenizer(String input) {
		this.input = input + '\0';
		this.tokens = new LinkedList<>();
	}

	public void process() {
		while (pos <= input.length() - 1) {
			char ch = input.charAt(pos);
			switch (state) {
			case START:
				handleStart(ch);
				break;
			case IDENTIFIER:
				handleIdentifier(ch);
				break;
			case STRING:
				handleString(ch);
				break;
			case INT:
				handleInt(ch);
				break;
			case FLOAT:
				handleFloat(ch);
				break;
			case COMMENT:
				handleComment(ch);
				break;
			}

			pos++;
		}
	}

	private void handleComment(char ch) {
		if (ch == '\n') {
			state = State.START;
			rowNum++;
		}
	}

	private void handleFloat(char ch) {
		if (!isDigit(ch)) {
			addToken(FLOAT, mark, pos);
			resetState();
		}
	}

	private void handleInt(char ch) {
		if (!isDigit(ch)) {
			switch (ch) {
			case '.':
				char c = lookAhead(1);
				if (isDigit(c)) {
					state = State.FLOAT;
					pos++;
				} else {
					throw new Error("小数点后应该跟着数字");
				}
				break;
			default:
				addToken(INT, mark, pos);
				resetState();
				break;
			}
		}
	}

	private void handleString(char ch) {
		switch (ch) {
		case '\n':
			throw new Error("字符串必须在一行");
		case '"':
			addToken(STRING, mark + 1, pos);
			state = State.START;
			break;
		}
	}

	private void handleIdentifier(char ch) {
		if (!isIdentifier(ch)) {
			TokenType type = keywordType.get(input.substring(mark, pos));
			if (type != null) {
				// 关键字
				addToken(type, mark, pos);
			} else {
				// 普通标识符
				addToken(IDENTIFIER, mark, pos);
			}
			resetState();
		}
	}

	private void handleStart(char ch) {
		switch (ch) {
		case ' ':
		case '\0':
		case '\t':
		case '\r':
			break;
		case '\n':
			rowNum++;
			break;
		case '(':
			addToken(LPAREN, 1);
			break;
		case ')':
			addToken(RPAREN, 1);
			break;
		case '[':
			addToken(LBRACKET, 1);
			break;
		case ']':
			addToken(RBRACKET, 1);
			break;
		case '{':
			addToken(LBRACE, 1);
			break;
		case '}':
			addToken(RBRACE, 1);
			break;
		case ',':
			addToken(COMMA, 1);
			break;
		case ';':
			addToken(SEMI, 1);
			break;
		case '.':
			addToken(DOT, 1);
			break;
		case '+':
			switch (lookAhead(1)) {
			case '+':
				addToken(INCR, 2);
				pos++;
				break;
			default:
				addToken(PLUS, 1);
				break;
			}
			break;
		case '-':
			switch (lookAhead(1)) {
			case '-':
				addToken(DECR, 2);
				pos++;
				break;
			default:
				addToken(MINUS, 1);
				break;
			}
			break;
		case '*':
			addToken(MULTIPLY, 1);
			break;
		case '/':
			addToken(DIVIDE, 1);
			break;
		case '%':
			addToken(MOD, 1);
			break;
		case '!':
			switch (lookAhead(1)) {
			case '=':
				addToken(NE, 2);
				pos++;
				break;
			default:
				addToken(NOT, 1);
				break;
			}
			break;
		case '=':
			switch (lookAhead(1)) {
			case '=':
				addToken(EQ, 2);
				pos++;
				break;
			default:
				addToken(ASSIGN, 1);
				break;
			}
			break;
		case '>':
			switch (lookAhead(1)) {
			case '=':
				addToken(GE, 2);
				pos++;
				break;
			default:
				addToken(GT, 1);
				break;
			}
			break;
		case '<':
			switch (lookAhead(1)) {
			case '=':
				addToken(LE, 2);
				pos++;
				break;
			default:
				addToken(LT, 1);
				break;
			}
			break;
		case '"':
			mark = pos;
			state = State.STRING;
			break;
		case '#':
			state = State.COMMENT;
			break;
		default:
			if (isIdentifierFirstChar(ch)) {
				mark = pos;
				state = State.IDENTIFIER;
			} else if (isDigit(ch)) {
				mark = pos;
				state = State.INT;
			} else {
				throw new Error("无法识别字符" + ch + ",第" + rowNum + "行");
			}
		}
	}

	private boolean isIdentifier(char ch) {
		return isCharacter(ch) || isUnderline(ch) || isDigit(ch);
	}

	private boolean isDigit(char ch) {
		return ch >= '0' && ch <= '9';
	}

	private boolean isIdentifierFirstChar(char ch) {
		return isCharacter(ch) || isUnderline(ch);
	}

	private boolean isUnderline(char ch) {
		return ch == '_';
	}

	private boolean isCharacter(char ch) {
		return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
	}

	private void addToken(TokenType tokenType, int strLen) {
		addToken(tokenType, pos, pos + strLen);
	}

	private void addToken(TokenType tokenType, int start, int end) {
		tokens.add(new Token(tokenType, input.substring(start, end)));
	}

	private char lookAhead(int i) {
		return input.charAt(pos + i);
	}

	private void resetState() {
		state = State.START;
		pos--;
	}

	public List<Token> getTokens() {
		return tokens;
	}

	public static boolean isKeyword(String keyword) {
		return keywordType.get(keyword) != null;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder("共" + rowNum + "行\n");

		for (Token token : tokens) {
			builder.append(token.tokenType + ":" + token.value + "\n");
		}

		return builder.toString();
	}

}
